/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package logic;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.table.*;

/**
 *  A TableModel that better supports the processing of rows of data. That
 *  is, the data is treated more like a row than an individual cell. Hopefully
 *  this class can be used as a parent class instead of extending the
 *  AbstractTableModel when you need custom models that contain row related
 *  data.
 *
 *  A few methods have also been added to make it easier to customize
 *  properties of the model, such as the column class and column editability.
 *
 *  Any class that extends this class must make sure to invoke the
 *  setRowClass() and setDataAndColumnNames() methods either directly,
 *  by using the various constructors, or indirectly.
 *
 */
abstract class RowTableModel<T> extends AbstractTableModel
{
	protected List<T> modelData;
	protected List<String> columnNames;
	protected Class[] columnClasses;
	protected Boolean[] isColumnEditable;
	private Class rowClass = Object.class;
	private boolean isModelEditable = true;

	/**
	 *  Constructs a <code>RowTableModel</code> with the row class.
	 *
	 *  This value is used by the getRowsAsArray() method.
	 *
	 *  Sub classes creating a model using this constructor must make sure
	 *  to invoke the setDataAndColumnNames() method.
	 *
	 * @param rowClass  the class of row data to be added to the model
	 */
	protected RowTableModel(Class rowClass)
	{
		setRowClass( rowClass );
	}

	/**
	 *  Constructs a <code>RowTableModel</code> with column names.
	 *
	 *  Each column's name will be taken from the <code>columnNames</code>
	 *  List and the number of colums is determined by thenumber of items
	 *  in the <code>columnNames</code> List.
	 *
	 *  Sub classes creating a model using this constructor must make sure
	 *  to invoke the setRowClass() method.
	 *
	 * @param columnNames	   <code>List</code> containing the names
	 *							of the new columns
	 */
	protected RowTableModel(List<String> columnNames)
	{
		this(new ArrayList<T>(), columnNames);
	}

	/**
	 *  Constructs a <code>RowTableModel</code> with initial data and
	 *  customized column names.
	 *
	 *  Each item in the <code>modelData</code> List must also be a List Object
	 *  containing items for each column of the row.
	 *
	 *  Each column's name will be taken from the <code>columnNames</code>
	 *  List and the number of colums is determined by thenumber of items
	 *  in the <code>columnNames</code> List.
	 *
	 *  Sub classes creating a model using this constructor must make sure
	 *  to invoke the setRowClass() method.
	 *
	 * @param modelData		 the data of the table
	 * @param columnNames	   <code>List</code> containing the names
	 *							of the new columns
	 */
	protected RowTableModel(List<T> modelData, List<String> columnNames)
	{
		setDataAndColumnNames(modelData, columnNames);
	}

	/**
	 *  Full Constructor for creating a <code>RowTableModel</code>.
	 *
	 *  Each item in the <code>modelData</code> List must also be a List Object
	 *  containing items for each column of the row.
	 *
	 *  Each column's name will be taken from the <code>columnNames</code>
	 *  List and the number of colums is determined by thenumber of items
	 *  in the <code>columnNames</code> List.
	 *
	 *  @param modelData	the data of the table
	 *  @param columnNames	<code>List</code> containing the names
	 *						of the new columns
	 *  @param rowClass     the class of row data to be added to the model
	 */
	protected RowTableModel(List<T> modelData, List<String> columnNames, Class rowClass)
	{
		setDataAndColumnNames(modelData, columnNames);
		setRowClass( rowClass );
	}

	/**
	 *  Reset the data and column names of the model.
	 *
	 *	A fireTableStructureChanged event will be generated.
	 *
	 * @param modelData		 the data of the table
	 * @param columnNames	   <code>List</code> containing the names
	 *							of the new columns
	 */
	protected void setDataAndColumnNames(List<T> modelData, List<String> columnNames)
	{
		this.modelData = modelData;
		this.columnNames = columnNames;
		columnClasses = new Class[getColumnCount()];
		isColumnEditable = new Boolean[getColumnCount()];
		fireTableStructureChanged();
	}

	/**
	 *  The class of the Row being stored in the TableModel
	 *
	 *  This is required for the getRowsAsArray() method to return the
	 *  proper class of row.
	 *
	 * @param rowClas		 the class of the row
	 */
	protected void setRowClass(Class rowClass)
	{
		this.rowClass = rowClass;
	}
//
//  Implement the TableModel interface
//
	/**
	 *  Returns the Class of the queried <code>column</code>.

	 *  First it will check to see if a Class has been specified for the
	 *  <code>column</code> by using the <code>setColumnClass</code> method.
	 *  If not, then the superclass value is returned.
	 *
	 *  @param column  the column being queried
	 *  @return the Class of the column being queried
	*/
	public Class getColumnClass(int column)
	{
		Class columnClass = null;

		//  Get the class, if set, for the specified column

		if (column < columnClasses.length)
			columnClass = columnClasses[column];

		//  Get the default class

		if (columnClass == null)
			columnClass = super.getColumnClass(column);

		return columnClass;
	}

	/**
	 * Returns the number of columns in this table model.
	 *
	 * @return the number of columns in the model
	 */
	public int getColumnCount()
	{
		return columnNames.size();
	}

	/**
	 * Returns the column name.
	 *
	 * @return a name for this column using the string value of the
	 * appropriate member in <code>columnNames</code>. If
	 * <code>columnNames</code> does not have an entry for this index
	 * then the default name provided by the superclass is returned
	 */
	public String getColumnName(int column)
	{
		Object columnName = null;

		if (column < columnNames.size())
		{
			columnName = columnNames.get( column );
		}

		return (columnName == null) ? super.getColumnName( column ) : columnName.toString();
	}

	/**
	 * Returns the number of rows in this table model.
	 *
	 * @return the number of rows in the model
	 */
	public int getRowCount()
	{
		return modelData.size();
	}

	/**
	 * Returns true regardless of parameter values.
	 *
	 * @param   row		   the row whose value is to be queried
	 * @param   column		the column whose value is to be queried
	 * @return				  true
	 */
	public boolean isCellEditable(int row, int column)
	{
		Boolean isEditable = null;

		//  Check is column editability has been set

		if (column < isColumnEditable.length)
			isEditable = isColumnEditable[column];

		return (isEditable == null) ? isModelEditable : isEditable.booleanValue();
	}
//
//  Implement custom methods
//
	/**
	 *  Adds a row of data to the end of the model.
	 *  Notification of the row being added will be generated.
	 *
	 * @param   rowData		 data of the row being added
	 */
	public void addRow(T rowData)
	{
		insertRow(getRowCount(), rowData);
	}

	/**
	 * Returns the Object of the requested <code>row</code>.
	 *
	 * @return the Object of the requested row.
	 */
	public T getRow(int row)
	{
		return modelData.get( row );
	}

	/**
	 * Returns an array of Objects for the requested <code>rows</code>.
	 *
	 * @return an array of Objects for the requested rows.
	 */
	@SuppressWarnings("unchecked")
	public T[] getRowsAsArray(int... rows)
	{
		List<T> rowData = getRowsAsList(rows);
		T[] array = (T[])Array.newInstance(rowClass, rowData.size());
		return (T[]) rowData.toArray( array );
	}

	/**
	 * Returns a List of Objects for the requested <code>rows</code>.
	 *
	 * @return a List of Objects for the requested rows.
	 */
	public List<T> getRowsAsList(int... rows)
	{
		ArrayList<T> rowData = new ArrayList<T>(rows.length);

		for (int i = 0; i < rows.length; i++)
		{
			rowData.add( getRow(rows[i]) );
		}

		return rowData;
	}
        /**
         * Returns a List with all the objects in the table.
         */
        public List <T> getRowsAsList (){
            return modelData; 
        }

	/**
	 *  Insert a row of data at the <code>row</code> location in the model.
	 *  Notification of the row being added will be generated.
	 *
	 *  @param   row	  row in the model where the data will be inserted
	 *  @param   rowData  data of the row being added
	 */
	public void insertRow(int row, T rowData)
	{
		modelData.add(row, rowData);
		fireTableRowsInserted(row, row);
	}

	/**
	 *  Insert multiple rows of data at the <code>row</code> location in the model.
	 *  Notification of the row being added will be generated.
	 *
	 * @param   row	  row in the model where the data will be inserted
	 * @param   rowList  each item in the list is a separate row of data
	 */
	public void insertRows(int row, List<T> rowList)
	{
		modelData.addAll(row, rowList);
		fireTableRowsInserted(row, row + rowList.size() - 1);
	}

	/**
	 *  Insert multiple rows of data at the <code>row</code> location in the model.
	 *  Notification of the row being added will be generated.
	 *
	 * @param   row	  row in the model where the data will be inserted
	 * @param   rowArray  each item in the Array is a separate row of data
	 */
	public void insertRows(int row, T[] rowArray)
	{
		List<T> rowList = new ArrayList<T>(rowArray.length);

		for (int i = 0; i < rowArray.length; i++)
		{
			rowList.add( rowArray[i] );
		}

		insertRows(row, rowList);
	}

	/**
	 *  Moves one or more rows from the inlcusive range <code>start</code> to
	 *  <code>end</code> to the <code>to</code> position in the model.
	 *  After the move, the row that was at index <code>start</code>
	 *  will be at index <code>to</code>.
	 *  This method will send a <code>tableRowsUpdated</code> notification
	 *  message to all the listeners. <p>
	 *
	 *  <pre>
	 *  Examples of moves:
	 *  <p>
	 *  1. moveRow(1,3,5);
	 *		  a|B|C|D|e|f|g|h|i|j|k   - before
	 *		  a|e|f|g|h|B|C|D|i|j|k   - after
	 *  <p>
	 *  2. moveRow(6,7,1);
	 *		  a|b|c|d|e|f|G|H|i|j|k   - before
	 *		  a|G|H|b|c|d|e|f|i|j|k   - after
	 *  <p>
	 *  </pre>
	 *
	 * @param   start	   the starting row index to be moved
	 * @param   end		 the ending row index to be moved
	 * @param   to		  the destination of the rows to be moved
	 * @exception  IllegalArgumentException
	 *					  if any of the elements would be moved out
	 *					  of the table's range
	 */
	public void moveRow(int start, int end, int to)
	{
		if (start < 0)
		{
			String message = "Start index must be positive: " + start;
			throw new IllegalArgumentException( message );
		}

		if (end > getRowCount() - 1)
		{
			String message = "End index must be less than total rows: " + end;
			throw new IllegalArgumentException( message );
		}

		if (start > end)
		{
			String message = "Start index cannot be greater than end index";
			throw new IllegalArgumentException( message );
		}

		int rowsMoved = end - start + 1;

		if (to < 0
		||  to > getRowCount() - rowsMoved)
		{
			String message = "New destination row (" + to + ") is invalid";
			throw new IllegalArgumentException( message );
		}

		//  Save references to the rows that are about to be moved

		ArrayList<T> temp = new ArrayList<T>(rowsMoved);

		for (int i = start; i < end + 1; i++)
		{
			temp.add(modelData.get(i));
		}

		//  Remove the rows from the current location and add them back
		//  at the specified new location

		modelData.subList(start, end + 1).clear();
		modelData.addAll(to, temp);

		//  Determine the rows that need to be repainted to reflect the move

		int first;
		int last;

		if (to < start)
		{
			first = to;
			last = end;
		}
		else
		{
			first = start;
			last = to + end - start;
		}

		fireTableRowsUpdated(first, last);
	}

	/**
	 *  Remove the specified rows from the model. Rows between the starting
	 *  and ending indexes, inclusively, will be removed.
	 *  Notification of the rows being removed will be generated.
	 *
	 * @param   start		 starting row index
	 * @param   end		   ending row index
	 * @exception  ArrayIndexOutOfBoundsException
	 *								if any row index is invalid
	 */
	public void removeRowRange(int start, int end)
	{
		modelData.subList(start, end + 1).clear();
		fireTableRowsDeleted(start, end);
	}

	/**
	 *  Remove the specified rows from the model. The row indexes in the
	 *  array must be in increasing order.
	 *  Notification of the rows being removed will be generated.
	 *
	 * @param   rows  array containing indexes of rows to be removed
	 * @exception  ArrayIndexOutOfBoundsException
	 *				  if any row index is invalid
	 */
	public void removeRows(int... rows)
	{
		for (int i = rows.length - 1; i >= 0; i--)
		{
			int row = rows[i];
			modelData.remove(row);
			fireTableRowsDeleted(row, row);
		}
	}

	/**
	 *  Replace a row of data at the <code>row</code> location in the model.
	 *  Notification of the row being replaced will be generated.
	 *
	 * @param   row	  row in the model where the data will be replaced
	 * @param   rowData  data of the row to replace the existing data
	 * @exception  IllegalArgumentException  when the Class of the row data
	 *			 does not match the row Class of the model.
	 */
	public void replaceRow(int row, T rowData)
	{
		modelData.set(row, rowData);
		fireTableRowsUpdated(row, row);
	}

	/**
	 * Sets the Class for the specified column.
	 *
	 * @param  column	   the column whose Class is being changed
	 * @param  columnClass  the new Class of the column
	 * @exception  ArrayIndexOutOfBoundsException
	 *						if an invalid column was given
	 */
	public void setColumnClass(int column, Class columnClass)
	{
		columnClasses[column] = columnClass;
		fireTableRowsUpdated(0, getRowCount() - 1);
	}

	/**
	 * Sets the editability for the specified column.
	 *
	 * @param  column	   the column whose Class is being changed
	 * @param  isEditable   indicates if the column is editable or not
	 * @exception  ArrayIndexOutOfBoundsException
	 *						if an invalid column was given
	 */
	public void setColumnEditable(int column, boolean isEditable)
	{
		isColumnEditable[column] = isEditable ? Boolean.TRUE : Boolean.FALSE;
	}

	/**
	 *  Set the ability to edit cell data for the entire model
	 *
	 *  Note: values set by the setColumnEditable(...) method will have
	 *  prioritiy over this value.
	 *
	 * @param isModelEditable  true/false
	 */
	public void setModelEditable(boolean isModelEditable)
	{
		this.isModelEditable = isModelEditable;
	}

	/*
	 *  Convert an unformatted column name to a formatted column name. That is:
	 *
	 *  - insert a space when a new uppercase character is found, insert
	 *    multiple upper case characters are grouped together.
	 *  - replace any "_" with a space
	 *
	 *  @param columnName  unformatted column name
	 *  @return the formatted column name
	 */
	public static String formatColumnName(String columnName)
	{
		if (columnName.length() < 3) return columnName;

		StringBuffer buffer = new StringBuffer( columnName );
		boolean isPreviousLowerCase = false;

		for (int i = 1; i < buffer.length(); i++)
		{
			boolean isCurrentUpperCase = Character.isUpperCase( buffer.charAt(i) );

			if (isCurrentUpperCase && isPreviousLowerCase)
			{
				buffer.insert(i, " ");
				i++;
			}

			isPreviousLowerCase = ! isCurrentUpperCase;
		}

		return buffer.toString().replaceAll("_", " ");
	}
}